﻿#if UNITY_EDITOR
//#define TRACK_STACK
#endif
#if UNITY_PS5
#define STRIP_TIME
#endif
using Devil;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Profiling;

namespace GameToolkit
{
    [System.Serializable]
    public struct AssetSetting
    {
        // 初始化资源表大小
        public int initCapacity;
        // ab文件夹
        public string abFolder;
        // manifest 文件
        public string manifestName;
        public string key;

        public static AssetSetting DefaultSetting
        {
            get
            {
                AssetSetting set;
                set.initCapacity = 512;
                string mani;
                switch (Application.platform)
                {
                    case RuntimePlatform.WindowsEditor:
                    case RuntimePlatform.WindowsPlayer:
                        if (System.IntPtr.Size == 8)
                            mani = "StandaloneWindows64";
                        else
                            mani = "StandaloneWindows";
                        break;
                    case RuntimePlatform.IPhonePlayer:
                        mani = "iOS";
                        break;
                    case RuntimePlatform.Android:
                        mani = "Android";
                        break;
                    case RuntimePlatform.LinuxEditor:
                    case RuntimePlatform.LinuxPlayer:
                        if (System.IntPtr.Size == 8)
                            mani = "Linux64";
                        else
                            mani = "Linux";
                        break;
                    default:
                        mani = "AssetsBundle";
                        break;
                }
                set.abFolder = NativeFileSystem.Combine(Application.streamingAssetsPath, mani);
#if UNITY_2021_1_OR_NEWER
                set.manifestName = null;
#else
                set.manifestName = mani;
#endif
#if ENCRYPT_ENABLED
                set.key = "k8foefoklMksdjYa";
#else
                set.key = null;
#endif
                return set;
            }
        }
    }

    public partial class AssetBundleUtil : AssetsUtil, ICommandHandler
    {
        // 允许同时加载资源数量
#if UNITY_PS5
        static int sMaxLoadBudget = 32;
#else
        static int sMaxLoadBudget = 64;
#endif
#if STRIP_TIME
#if UNITY_PS5
        static long sStripTicks = 100000; // 10ms
#else
        static long sStripTicks = 400000; // 40ms
#endif
#endif
        internal struct AbAsset
        {
            public int ab;
            public int id;
            public string path;
            //public AbAddress ab;

            public AbAsset(int abid, string path)
            {
                id = ParallelUtils.IgnoreCaseToHash(path);
                this.ab = abid;
                this.path = path;
            }
        }

        protected AssetSetting mSettings;

        private SortedList<AbAddress> mAbList;
        private List<AbAddress> mVariants;
        private AvlTree<AbAsset> mAbAssetMap;
        private UnloadAction mUnloadAction;
        private HashSet<AssetProcessor> mUnloading;
        protected readonly object mLock = new object();

        //HashSet<Asset> mExecCommands;
        AvlTree<AssetProcessor> mAssets;
        LoadAssetPipeline mPipeline;
        //RandomList<IExecutableProgress> mExecutions;
        ParallelDispatcher.Handler mPrepareExecution;
        bool mIsInitialized;
        public override UnloadAction CurrentUnloadAction => mUnloadAction;
        //public override bool IsAsyncDone { get { return !mPipeline.IsLooping; } }

        ParallelDispatcher.Handler ICommandHandler.GetHandler()
        {
            return mPrepareExecution;
        }

        public AssetBundleUtil(AssetSetting setting, AssetBundleVariant keywords) : base(keywords)
        {
            if (!setting.abFolder.EndsWith("/"))
                setting.abFolder = setting.abFolder + "/";
            mSettings = setting;
            mAbList = new SortedList<AbAddress>(32, (x) => x.Id);
            mAssets = new AvlTree<AssetProcessor>((x) => x.Id, (a, b) => a == b);
            mVariants = new List<AbAddress>();
            mAbAssetMap = new AvlTree<AbAsset>((x) => x.id);

            //mExecutions = new RandomList<IExecutableProgress>(64, (x, y) => x == y);

            mUnloading = new HashSet<AssetProcessor>();

            mPipeline = new LoadAssetPipeline(this);

        }

        public override IYieldInstruction GetLoadInstruction() { return mPipeline; }


        public void GetAbList(ICollection<IAssetPtr> abs, FilterDelegate<IAssetPtr> filter)
        {
            lock (mLock)
            {
                for (int i = 0; i < mAbList.Count; i++)
                {
                    var ab = mAbList[i];
                    if (filter == null || filter(ab))
                        abs.Add(ab);
                }
            }
        }

        protected override void OnBecomeActive()
        {
            base.OnBecomeActive();
            //var initVaraint = ABVariant.InitVariants(false); // SubThreading.Execute(ABVariant.InitVariantPattern, true);
            var prepare = new PrepareCmd(this);
            mPrepareExecution = prepare.Schedule(false);

        }

        protected override void OnDestroy()
        {
            base.OnDestroy();
            mPrepareExecution.Abort(true);
            mIsInitialized = false;
            mUnloadAction = UnloadAction.ForceUnload;
            if (MainThread.IsMainThread)
                UnloadUnusedAsset();
            else
                MainThread.RunOnMainThread(UnloadUnusedAsset);
        }

        public override bool IsAssetExists(string asset)
        {
            if (asset.StartsWithIgnoreCase("assets/"))
            {
                var id = ParallelUtils.IgnoreCaseToHash(asset);
                return mAbAssetMap.ContainsId(id);
            }
            else
            {
                var file = ParallelUtils.Concat(mSettings.abFolder, asset);
                return NativeFileSystem.HasFile(file);
            }
        }

        protected virtual AssetBundleCreateRequest LoadAssetBundle(string ab)
        {
            var file = ParallelUtils.Concat(mSettings.abFolder, ab);
#if CHINA_VERSION
            if (!string.IsNullOrEmpty(mSettings.key))
                AssetBundle.SetAssetBundleDecryptKey(mSettings.key);
#endif
            return AssetBundle.LoadFromFileAsync(file);
        }

        internal override ParallelDispatcher.Handler ClearVariant(SliceList<IAssetVariantGroup> groups)
        {
            mPrepareExecution = ParallelUtils.Schedule(UnloadVariantAsset, groups, true, mPrepareExecution);
            return mPrepareExecution;
        }

        void UnloadVariantAsset(SliceList<IAssetVariantGroup> groups)
        {
#if UNITY_EDITOR
            Debug.Log("Unload Variant Assets");
            Profiler.BeginSample("Unload Variant Assets");
#endif
            ErrorHandler errCall = null;
            lock (mLock)
            {
                for (int i = 0; i < mVariants.Count; i++)
                {
                    var ab = mVariants[i];
                    if (groups.Contains(ab.VariantGroup))
                        ab.Unload(UnloadAction.ForceUnload, ref errCall);
                }
            }
            errCall?.Invoke(null, "interrupt load");
#if UNITY_EDITOR
            Profiler.EndSample();
#endif
        }

        protected virtual Stream GetAbInfoStream()
        {
#if UNITY_EDITOR
            var abinfo = "Assets/AssetBundleInfo.txt";
#else
            var abinfo = ParallelUtils.Concat(mSettings.abFolder, "AssetBundleInfo.txt");
#endif
            if (File.Exists(abinfo))
            {
                return File.OpenRead(abinfo);
            }
            else
            {
                return null;
            }
        }

        protected virtual Stream GetAbTreeStream()
        {
#if UNITY_EDITOR
            var abinfo = "Assets/AssetBundleInfo.tree";
#else
            var abinfo = ParallelUtils.Concat(mSettings.abFolder, "AssetBundleInfo.tree");
#endif
            if (File.Exists(abinfo))
                return File.OpenRead(abinfo);
            else
                return null;
        }

        protected virtual void OnPrepareBegin() { }

        protected virtual void OnPrepareEnd()
        {
            if (IsValid)
            {
                mPrepareExecution = default;
                mIsInitialized = true;
            }
        }

        public AssetProcessor GetAsset<T>(AssetID id) where T : Object
        {
            //var pathid = StringUtil.IgnoreCaseToHash(id.path);
            var assetId = AssetProcessor.IdentifyAsset<T>(id.id);
            lock (mLock)
            {
                if (typeof(T) == typeof(AssetBundle))
                {
                    var ab = mAbList.GetValue(assetId);
                    if (ab == null && !mIsInitialized)
                    {
                        ab = new AbAddress(this, assetId, id.path);
                        mAbList.Add(ab);
                    }
//#if UNITY_EDITOR
                    if (ab == null)
                        Debug.LogWarningFormat("AssetBundle@\"{0}\" doesn't exists", id.path);
//#endif
                    return ab;
                }
                else
                {
                    var asset = mAssets.GetData(assetId);
                    if (asset == null)
                    {
                        if(id.useAb && mIsInitialized && !mAbAssetMap.ContainsId(id.id))
                        {
//#if UNITY_EDITOR
                            Debug.LogWarningFormat("Asset<{0}>@\"{1}\" doesn't exists", typeof(T).Name, id.path);
//#endif
                            return null;
                        }
                        asset = new AssetAddress<T>(this, assetId, id);
                        mAssets.Add(asset);
                    }
                    else if (!(asset is IAssetCache<T>) || !asset.path.EqualsIgnoreCase(id.path))
                    {
                        Debug.LogErrorFormat("Asset<{0}>@\"{1}\" conflect to Asset<{2}>@\"{3}\"", asset.AssetType, asset.path, typeof(T), id.path);
                        asset = new AssetAddress<T>(this, assetId, id);
                    }
                    return asset;
                }
            }
        }

        public override T LoadAsset<T>(string assetPath)
        {
            var id = AssetID.Auto(assetPath);
            var asset = GetAsset<T>(id);
            var cache = asset as IAssetCache<T>;
            if (cache != null)
            {
                return cache.Asset as T;
            }
            return null;
        }

        public override IAssetCache<T> LoadAssetAsync<T>(string path)
        {
            var id = AssetID.Auto(path);
            var asset = GetAsset<T>(id);
            var cache = asset as IAssetCache<T>;
            if (cache != null)
            {
                asset.ScheduleCommand(mPrepareExecution);
            }
            return cache;
        }

        protected override void UnloadUnusedAsset(UnloadAction action)
        {
            if (action > mUnloadAction)
            {
                mUnloadAction = action;
            }
            //UnloadUnusedAsset();
        }

        private void UnloadUnusedAsset()
        {
#if UNITY_EDITOR
            Profiler.BeginSample("Unload Unused Assets");
#endif
            ErrorHandler errCall = null;
            lock (mLock)
            {
                var action = mUnloadAction;
                mUnloadAction = UnloadAction.None;
                if (action == UnloadAction.ForceUnload || action == UnloadAction.Cleanup)
                {
#if UNITY_EDITOR
                    RTLog.LogFormat(LogCat.Asset, "Unload Unused Assets ({0})", action);
#endif
                    foreach (var ab in mAbList)
                    {
                        //mAssets.Remove(ab);
                        ab.Unload(action, ref errCall);
                    }
                    mUnloading.Clear();
                    //Resources.UnloadUnusedAssets();
                }
                else if (action == UnloadAction.Unused)
                {
#if UNITY_EDITOR
                    RTLog.LogFormat(LogCat.Asset, "Unload Unused Assets ({0}, assets * {1})", action, mUnloading.Count);
#endif
                    foreach (var ab in mUnloading)
                    {
                        ab.Unload(action, ref errCall);
                    }
                    mUnloading.Clear();
                    //Debug.Log("Unload assetsbundle");
                    //Resources.UnloadUnusedAssets();
                }
            }
            errCall?.Invoke(null, "interrupt load");
#if UNITY_EDITOR
            Profiler.EndSample();
#endif
            //SubThreading.Execute(UnloadUnusedAsset, UnloadAction.Cleanup, mPrepareExecution, EExecuteEvent.Update);
        }

        public override IAssetPtr LoadAllAssets<T>(string abname, string regex,
            AssetHandler<T> handler, AssetHandler<AssetBundle> finalHandler, ErrorHandler errorHandler)
        {
            var cmd = new LoadAllAssetCmd<T>(this, abname);
            cmd.regex = string.IsNullOrEmpty(regex) ? null : new Regex(regex, RegexOptions.IgnoreCase);
            cmd.finalHandler = finalHandler;
            cmd.errHandler = errorHandler;
            cmd.handler = handler;
            cmd.cmdHandler = ParallelUtils.Schedule(cmd, false, mPrepareExecution);
            return cmd;
        }

        public override ParallelDispatcher.Handler FindAllAssets(string abname, string regex, ICollection<string> results)
        {
            if (string.IsNullOrEmpty(abname) || results == null)
                return default;
            var cmd = new FindAllAssetCmd(this, abname, regex, results);
            return ParallelUtils.Schedule(cmd, false, mPrepareExecution);
        }

        class FindAllAssetCmd : IYieldCommand
        {
            string abname;
            AssetBundleUtil util;
            string[] assets;
            string regex;
            ICollection<string> results;
            public FindAllAssetCmd(AssetBundleUtil util, string ab, string regex, ICollection<string> result)
            {
                this.util = util;
                this.abname = ab;
                this.regex = regex;
                this.results = result;
            }

            void GetAssets(IAssetPtr asset)
            {
                var ab = asset.Asset as AssetBundle;
                if (ab != null)
                {
                    assets = ab.GetAllAssetNames();
                }
            }

            public IEnumerator Execute()
            {
                var pattern = string.IsNullOrEmpty(regex) ? null : new Regex(regex, RegexOptions.IgnoreCase);
                // get from abmap 
                var abid = AssetProcessor.IdentifyAsset<AssetBundle>(abname);
                foreach(var asset in util.mAbAssetMap)
                {
                    if (asset.ab != abid)
                        continue;
                    if (pattern == null || pattern.IsMatch(asset.path))
                        results.Add(asset.path);
                }
                yield break;

                // get from assetbundle
                //var ab = util.LoadAssetAsync<AssetBundle>(abname);
                //yield return ParallelUtils.Schedule(GetAssets, ab, true, ab.GetParallelHandler());
                //if (assets == null)
                //    yield break;
                //for (int i = 0; i < assets.Length; i++)
                //{
                //    var path = assets[i];
                //    if (pattern == null || pattern.IsMatch(path))
                //        results.Add(path);
                //}
            }
        }


        class LoadAllAssetCmd<T> : IAssetPtr, ICommandHandler, IYieldCommand, IBreakCommandSignal where T : Object
        {
            AssetBundleUtil util;
            internal AssetHandler<AssetBundle> finalHandler;
            internal AssetHandler<T> handler;
            internal ErrorHandler errHandler;
            internal ParallelDispatcher.Handler cmdHandler;
            internal AbAddress abcache;
            internal Regex regex;
            HashSet<IAssetCache<T>> mRegists;
            string[] assets;
            public string Name { get { return abcache.Name; } }
            public ELoadResult LoadResult { get; private set; }
            public int Id { get { return abcache.id; } }
            public Object Asset { get { return abcache.Asset; } }
            public bool IsReferenced => abcache != null && abcache.IsReferenced;

            public override string ToString()
            {
                return string.Format("load all {0} in \"{1}\"", typeof(T).Name, abcache.Name);
            }

            public ParallelDispatcher.Handler GetHandler()
            {
                return cmdHandler;
            }

            public LoadAllAssetCmd(AssetBundleUtil util, string abname)
            {
                this.util = util;
                var id = AssetID.ResAsset(abname);
                var assetId = AssetProcessor.IdentifyAsset<AssetBundle>(id.id);
                lock (util.mLock)
                {
                    abcache = util.mAbList.GetValue(assetId);
                    if (abcache == null)
                    {
                        abcache = new AbAddress(util, assetId, abname);
                        util.mAbList.Add(abcache);
                    }
                }
            }

            public void OnBreak()
            {
                LoadResult = ELoadResult.Faild;
                if(mRegists != null)
                {
                    foreach(var v in mRegists)
                    {
                        v.Unregist(handler, errHandler);
                    }
                }
                if (abcache != null)
                    abcache.Unregist(null, errHandler);
            }

            void GetAssets()
            {
                var ab = abcache == null ? null : abcache.AssetInstance;
                if (ab != null)
                    assets = ab.GetAllAssetNames();
            }

            void WaitMainThread() { }

            public IEnumerator Execute()
            {
                LoadResult = ELoadResult.Loading;
                if (abcache == null)
                {
                    LoadResult = ELoadResult.Faild;
                    yield break;
                }
                if (handler != null || errHandler != null)
                {
                    if (mRegists == null)
                        mRegists = new HashSet<IAssetCache<T>>();
                    else
                        mRegists.Clear();
                }
                abcache.Regist(null, errHandler);
                var stat = abcache.result;
                abcache.ScheduleCommand(util.mPrepareExecution);
                //yield return abcache.GetHandler();
                yield return ParallelUtils.Schedule(GetAssets, true, abcache.GetHandler());
                //var bundle = abcache.AssetInstance;
                if (assets == null)
                    Debug.LogErrorFormat("{0} is empty ({1}).", abcache.Name, abcache.result);
                if (assets != null && assets.Length > 0)
                {
                    var combine = ParallelUtils.NewCombination(string.Format("load all assets in \"{0}\"", abcache.Name));
                    for (int i = 0; i < assets.Length; i++)
                    {
                        var path = assets[i];
                        if (regex == null || regex.IsMatch(path))
                        {
                            var ex = util.LoadAssetAsync<T>(path);
                            if (ex != null)
                            {
                                if (mRegists != null)
                                {
                                    ex.Regist(handler, errHandler);
                                    mRegists.Add(ex);
                                }
                            }
                            else if (errHandler != null)
                            {
                                errHandler(null, string.Format("Don't find asset \"{0}\"", path));
                            }
                            combine.Combine(ParallelUtils.GetParallelHandler(ex));
                        }
                    }
                    combine.Combine(ParallelUtils.Schedule(WaitMainThread, true));
                    yield return combine;
                }
                yield return ParallelUtils.Schedule(Complete, true);
            }

            void Complete()
            {
                LoadResult = ELoadResult.Success;
                finalHandler?.Invoke(abcache, abcache.AssetInstance);
            }

            public void Retain()
            {
                abcache.Retain();
            }

            public void Release()
            {
                abcache.Release();
            }
        }

        class PrepareCmd : IYieldCommand
        {
            AssetBundleUtil util;
            HashSet<AbAddress> abList;
            //bool parseManifest;

            internal PrepareCmd(AssetBundleUtil util)
            {
                this.util = util;
            }

            public IEnumerator Execute()
            {
                ParallelDispatcher.Handler initDirectDependency = default;
                ParallelDispatcher.Handler cleanUp = default;
                util.Variants?.Initialize();
                util.OnPrepareBegin();
                var parseAb = util.IsValid && string.IsNullOrEmpty(util.mSettings.manifestName) && ParseABDependencies();
                if (!parseAb)
                {
                    var manifestName = util.mSettings.manifestName;
                    if (string.IsNullOrEmpty(manifestName))
                    {
                        manifestName = NativeFileSystem.GetFileName(util.mSettings.abFolder.Substring(0, util.mSettings.abFolder.Length - 1), false);
                    }
                    var id = AssetProcessor.IdentifyAsset<AssetBundle>(manifestName);
                    var ab = new AbAddress(util, id, manifestName);
                    ab.ScheduleCommand(default);
                    ab.Retain();
                    id = AssetProcessor.IdentifyAsset<AssetBundleManifest>("AssetBundleManifest");
                    AssetID asset = AssetID.AbAsset("AssetBundleManifest");
                    var manifest = new AssetAddress<AssetBundleManifest>(util, id, asset, ab);
                    //manifest.Regist(HandleManifest, null);
                    manifest.ScheduleCommand(ab.GetHandler());
                    yield return ParallelUtils.Schedule(HandleManifest, manifest, true, manifest.GetHandler());
                    initDirectDependency = ParallelUtils.Schedule(InitDirectDependencies, manifest, true);
                    cleanUp = ParallelUtils.Schedule(ab.Release, false, initDirectDependency);
                }
                abList = new HashSet<AbAddress>();
                lock (util.mLock)
                {
                    foreach(var data in util.mAbList)
                    {
                        abList.Add(data);
                    }
                }
                var initDependency = ParallelUtils.ScheduleParallel<AbAddress>(InitAllDependencies, abList, initDirectDependency);

                var dependencyJob = ParallelUtils.Schedule(ReleaseDirectDependencies, false, initDependency);
                var parseAssetsJob = ParallelUtils.Schedule(ParseAssets, false);
                var variantsJob = ParallelUtils.Schedule(InitVariants, false);

                var waitJobs = ParallelUtils.NewCombination("Wait Parse Assets");
                waitJobs.Combine(dependencyJob);
                waitJobs.Combine(parseAssetsJob);
                waitJobs.Combine(variantsJob);
                waitJobs.Combine(cleanUp);
                yield return waitJobs;

                util.mUnloadAction = UnloadAction.Unused;
#if UNITY_EDITOR
                //yield return SubThreading.Execute(() => Debug.Break(), EExecuteEvent.Update);
                //Profiler.BeginSample("Check AB Dependency Conflict");
                //// 检查相互依赖的 ab 包
                //lock (util.mLock)
                //{
                //    foreach (var item in util.mAbList)
                //    {
                //        item.CheckDependencyConflict();
                //    }

                //}
                //Profiler.EndSample();
#endif
                util.OnPrepareEnd();
            }

            AbAddress InitAb(string ab)
            {
                string abname, variant;
                var keywordGroup = util.Variants.GetABInfo(ab, out abname, out variant);
                var abid = AssetProcessor.IdentifyAsset<AssetBundle>(abname);
                var abmeta = util.mAbList.GetValue(abid);
                if (abmeta == null)
                {
                    abmeta = new AbAddress(util, abid, abname);
                    util.mAbList.Add(abmeta);
                }
                if (keywordGroup != null)
                {
                    abmeta.AddVariant(keywordGroup, variant);
                }
                return abmeta;
            }

            void HandleManifest(IAssetPtr ptr/*, AssetBundleManifest manifest*/)
            {
                var manifest = ptr.Asset as AssetBundleManifest;
                if (manifest == null)
                    return;
#if UNITY_EDITOR
                Profiler.BeginSample("Parse AB Manifest");
#endif
                var abs = manifest.GetAllAssetBundles();
                lock (util.mLock)
                {
                    var len = abs == null ? 0 : abs.Length;
                    for (int i = 0; i < len; i++)
                    {
                        var ab = abs[i];
                        var abmeta = InitAb(ab);
                    }
                }

#if UNITY_EDITOR
                Profiler.EndSample();
#endif
            }

            bool ParseABDependencies()
            {
                bool result = false;
                Stream stream = null;
                try
                {
                    stream = util.GetAbTreeStream();
                    if (stream == null)
                        return false;
                    string str;
                    var reader = new StreamReader(stream);
                    AbAddress ab = null;
                    while ((str = reader.ReadLine()) != null)
                    {
                        str = str.Trim();
                        if (string.IsNullOrEmpty(str))
                        {
                            ab = null;
                            continue;
                        }
#if UNITY_PS5
                        str = str.Replace('\\', '/');
#endif
                        if (str[0] == '@')
                        {
                            //int abid;
                            var abname = str.Substring(1);
                            lock (util.mLock)
                            {
                                ab = InitAb(abname);
                                ab.directDependencies = new HashSet<AbAddress>();
                            }
                        }
                        else if (ab != null)
                        {
                            lock (util.mLock)
                            {
                                var dependent = InitAb(str);
                                ab.directDependencies.Add(dependent);
                            }
                        }
                    }
                    stream.Close();
                    reader.Dispose();
                    result = true;
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
                finally
                {
                    if (stream != null)
                    {
                        stream.Dispose();
                    }
                }
                return result;
            }

            void GetDirectDependencies(AssetBundleManifest manifest, string ab, HashSet<AbAddress> dependencies)
            {
                var asset = manifest.GetDirectDependencies(ab);// GetAllDependencies(ab);
                if (asset != null && asset.Length > 0)
                {
                    for (int i = 0; i < asset.Length; i++)
                    {
                        var dep = asset[i];
                        string abname, variant;
                        var group = util.Variants.GetABInfo(dep, out abname, out variant);
                        //if (ABVariant.IsVariantAB(dep))
                        //{
                        //    var n = dep.LastIndexOf('.');
                        //    dep = dep.Substring(0, n);
                        //}
                        var id = AssetProcessor.IdentifyAsset<AssetBundle>(abname);
                        var depab = util.mAbList.GetValue(id);
                        if (depab != null)
                            dependencies.Add(depab);
                    }
                }
            }

            internal void InitDirectDependencies(IAssetPtr manifestPtr)
            {
                var manifest = manifestPtr.Asset as AssetBundleManifest;
                if (manifest == null)
                    return;

#if UNITY_EDITOR
                Profiler.BeginSample("Parse AB Dependencies");
#endif
                foreach (var abmeta in abList)
                {
                    var variants = abmeta.Variants;
                    abmeta.directDependencies = new HashSet<AbAddress>();
                    //dependencies.Clear();
                    if (variants.Count > 0)
                    {
                        for (int i = 0; i < variants.Count; i++)
                        {
                            var path = ParallelUtils.Concat(abmeta, '.', variants[i]);
                            GetDirectDependencies(manifest, path, abmeta.directDependencies);
                        }
                    }
                    else
                    {
                        var path = abmeta.path;
                        GetDirectDependencies(manifest, path, abmeta.directDependencies);
                    }
                }
#if UNITY_EDITOR
                Profiler.EndSample();
#endif
            }

            internal void InitAllDependencies(AbAddress ab)
            {
                if(ab.directDependencies == null)
                {
                    ab.SetDependencies(null);
                }
                else
                {
                    var set = new HashSet<AbAddress>();
                    var stack = new Stack<AbStack>(ab.directDependencies.Count + 5);
                    stack.Push(new AbStack(ab, 0));
                    int depth = 0;
                    while (stack.Count > 0)
                    {
                        var dep = stack.Pop();
                        if (dep.ab.directDependencies == null)
                            continue;
                        foreach (var sub in dep.ab.directDependencies)
                        {
                            if (sub != ab && set.Add(sub))
                            {
                                var push = new AbStack(sub, dep.depth + 1);
                                stack.Push(push);
                                if (depth < push.depth)
                                    depth = push.depth;
                            }
                        }
                    }
                    ab.SetDependencies(set);
                    ab._dependencyDepth = depth;
                    //var buf = new StringBuilder();
                    //buf.Append("AB: ").Append(ab.Name).Append(" dependents: ").AppendLine();
                    //foreach (var sub in set)
                    //{
                    //    buf.AppendTab(1).Append(sub.Name).AppendLine();
                    //}
                    //Debug.Log(buf.ToString());
                }
            }

            internal void InitVariants()
            {
                util.mVariants.Clear();
                //lock (util.mLock)
                //{
                foreach (var data in abList)
                {
                    if (data.HasVariant)
                        util.mVariants.Add(data);
                }
                //}
            }

            internal void ReleaseDirectDependencies()
            {
                //lock (util.mLock)
                {
                    foreach (var ab in abList)
                    {
                        ab.directDependencies = null;
                    }
                }
            }

            void ParseAssets()
            {
                Stream stream = null;
                try
                {
                    stream = util.GetAbInfoStream();
                    var reader = new StreamReader(stream);
                    string str;
                    AbAddress ab = null;
                    bool hasVariant = false;
                    IAssetVariantGroup keywordGroup = null;
                    //Regex pattern = null;
                    //MatchEvaluator repalce = ABVariant.VariantAssetEvaluator;
                    while ((str = reader.ReadLine()) != null)
                    {
                        str = str.Trim();
                        if (string.IsNullOrEmpty(str))
                        {
                            ab = null;
                            continue;
                        }
                        if (str[0] == '@')
                        {
                            //var abname = str.Substring(1);
                            string abname, variant;
                            keywordGroup = util.Variants.GetABInfo(str.Substring(1), out abname, out variant);

                            hasVariant = keywordGroup != null; // !string.IsNullOrEmpty(variant);// ABVariant.IsVariantAsset(abname);
                            //if (hasVariant)
                            //{
                            //    //var n = abname.LastIndexOf('.');
                            //    pattern = ABVariant.GetVariantAssetReplacePattern(variant);
                            //    //abname = abname.Substring(0, n);
                            //}
                            var abid = AssetProcessor.IdentifyAsset<AssetBundle>(abname);
                            lock (util.mLock)
                            {
                                ab = util.mAbList.GetValue(abid);
                                if (ab == null)
                                {
                                    ab = new AbAddress(util, abid, abname);
                                    util.mAbList.Add(ab);
                                }
                            }
                            //Debug.LogFormat("[AssetMap] @{0}", ab.Name);
                        }
                        else if (ab != null)
                        {
                            AbAsset abasset;
                            if (hasVariant)
                                str = keywordGroup.GetAssetAddress(str);// pattern.Replace(str, repalce);
                            abasset.id = AssetID.HashID(str);
                            abasset.ab = ab.id;
                            abasset.path = str;
                            lock (util.mLock)
                            {
                                util.mAbAssetMap.Add(abasset);
                            }
                        }
                    }

                    stream.Close();
                    reader.Dispose();
                }
                catch (System.Exception e)
                {
                    Debug.LogException(e);
                }
                finally
                {
                    if (stream != null)
                    {
                        stream.Dispose();
                    }
                }
            }

            internal struct AbStack
            {
                public AbAddress ab;
                public int depth;
                public AbStack(AbAddress ab, int depth)
                {
                    this.ab = ab;
                    this.depth = depth;
                }
            }
        }
    }
}